package my.study.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

/**
 * 参考com.imooc.netty.samples.$06.EchoServer
 * <p>
 * 测试方法
 * 1) cmd中输入"telnet localhost 8088"、然后随便输入字符即可
 * 2) 配套客户端类StudyNettyClient
 * <p>
 * 补充:telnet退出方法,先按"ctrl+]"、然后输入"quit"即可
 */
public class StudyNettyServer {

    static final int PORT = Integer.parseInt(System.getProperty("port", "8088"));

    public static void main(String[] args) {

        /*
         * 1. 声明线程池（必须）
         * 注:bossGroup的线程大小一般均使用1
         */
        EventLoopGroup bossGroup = Epoll.isAvailable() ? new EpollEventLoopGroup(1) : new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup();

        try {
            /*
             * 2. 服务端引导器（必须）
             */
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            /*
             * 3. 设置线程池（必须）
             * 注:下面的代码,可以连续打点设置、也可以分开单独设置.写法不同效果一样(此处单独写,方便加注释)
             */
            serverBootstrap.group(bossGroup, workerGroup);
            /*
             * 4. 设置ServerSocketChannel的类型（必须）
             */
            serverBootstrap.channel(Epoll.isAvailable() ? EpollServerSocketChannel.class : NioServerSocketChannel.class);
            /*
             * 5. 设置参数（可选）
             * ChannelOption.SO_BACKLOG: tcp/ip协议listen函数中的backlog参数,等待连接池的大小
             */
            serverBootstrap.option(ChannelOption.SO_BACKLOG, 100);
            /*
             * 6. 设置ServerSocketChannel对应的Handler，只能设置一个（即设置Handler、可选）
             */
            serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));
            /*
             * 7. 设置SocketChannel对应的Handler（即编写并设置子Handler、必须）
             */
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    ChannelPipeline channelPipeline = socketChannel.pipeline();
                    // 可以添加多个子Handler
                    // channelPipeline.addLast(new LoggingHandler(LogLevel.INFO));
                    channelPipeline.addLast(new StringDecoder());
                    channelPipeline.addLast(new StringEncoder());
                    channelPipeline.addLast(new EchoServerHandler());//自定义handler
                }
            });

            /*
             * 8. 绑定端口（必须）
             * 注:下面两个sync()方法会抛InterruptedException异常
             */
            ChannelFuture channelFuture = serverBootstrap.bind(PORT).sync();
            /*
             * 9. 等待服务端监听端口关闭，这里会阻塞主线程（必须）
             */
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            /*
             * 10. 优雅地关闭两个线程池（建议）
             */
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
